package com.atguigu.tingshu.album.service.impl;

import com.atguigu.tingshu.album.mapper.AlbumAttributeValueMapper;
import com.atguigu.tingshu.album.mapper.AlbumInfoMapper;
import com.atguigu.tingshu.album.mapper.AlbumStatMapper;
import com.atguigu.tingshu.album.service.AlbumInfoService;
import com.atguigu.tingshu.common.cache.TsCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.AlbumStat;
import com.atguigu.tingshu.query.album.AlbumInfoQuery;
import com.atguigu.tingshu.vo.album.AlbumAttributeValueVo;
import com.atguigu.tingshu.vo.album.AlbumInfoVo;
import com.atguigu.tingshu.vo.album.AlbumListVo;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tencentcloudapi.ame.v20190916.models.Album;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.statement.select.Wait;
import org.jetbrains.annotations.Nullable;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Random;

@Slf4j
@Service
@SuppressWarnings({"all"})
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {

    @Autowired
    private AlbumInfoMapper albumInfoMapper;
    @Autowired
    private AlbumAttributeValueMapper albumAttributeValueMapper;
	@Autowired
	private AlbumStatMapper albumStatMapper;
	@Autowired
	private RabbitService rabbitService;

	@Autowired
	private RedisTemplate redisTemplate;

	@Autowired
	private RedissonClient redissonClient;

	/**
	 * 删除专辑
	 * @param albumId
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void reremoveAlbumInfo(Long albumId) {
		//主键字段，可以使用mybatis-plus提供的快捷方法
		//非主键字段需要使用wapper构建查询条件
		//删除专辑信息
		albumInfoMapper.deleteById(albumId);
		//删除属性值信息
		albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumId));
		//删除统计信息
		albumStatMapper.delete(new LambdaQueryWrapper<AlbumStat>().eq(AlbumStat::getAlbumId,albumId));
		//删除声音(根据业务需求去判定跟声音的关联)

	}

	/**
	 * 专辑分页列表查询
	 * @param page
	 * @param albumInfoQuery
	 * @return
	 */
	@Override
	public IPage<AlbumListVo> findUserAlbumPage(Page<AlbumListVo> page, AlbumInfoQuery albumInfoQuery) {

		return albumInfoMapper.selectUserAlbumPage(page,albumInfoQuery);
	}

	/**
     * 保存专辑
     *
     * @param albumInfoVo
     * @param userid
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveAlbumInfo(AlbumInfoVo albumInfoVo, Long userid) {
		try {
			//1.保存专辑信息表
			AlbumInfo albumInfo = new AlbumInfo();
			//属性赋值
			BeanUtils.copyProperties(albumInfoVo,albumInfo);
			//补充必要属性
			albumInfo.setUserId(userid);
			//如果不是付费类型的专辑，提供五集试听
			if(!SystemConstant.ALBUM_PAY_TYPE_FREE.equals(albumInfoVo.getPayType())){
				albumInfo.setTracksForFree(5);
			}
			//设置专辑审核通过
			albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);
			//存入主表数据
			albumInfoMapper.insert(albumInfo);
			//保存标签
			List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
			if(!albumAttributeValueVoList.isEmpty()){
				for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
					AlbumAttributeValue albumAttributeValue=new AlbumAttributeValue();
					albumAttributeValue.setAlbumId(albumInfo.getId());
					albumAttributeValue.setAttributeId(albumAttributeValueVo.getAttributeId());
					albumAttributeValue.setValueId(albumAttributeValueVo.getValueId());
					albumAttributeValueMapper.insert(albumAttributeValue);
				}
			}
			saveAlbumStat(albumInfo.getId(), SystemConstant.ALBUM_STAT_PLAY);
			saveAlbumStat(albumInfo.getId(), SystemConstant.ALBUM_STAT_SUBSCRIBE);
			saveAlbumStat(albumInfo.getId(), SystemConstant.ALBUM_STAT_BROWSE);
			saveAlbumStat(albumInfo.getId(), SystemConstant.ALBUM_STAT_COMMENT);

			//  判断当前专辑是公开; is_open=1;
			if ("1".equals(albumInfo.getIsOpen())){
				//  调用上架方法;
				rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM,MqConst.ROUTING_ALBUM_UPPER, albumInfo.getId());
				//  将专辑Id添加到布隆过滤器;
				RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
				bloomFilter.add(albumInfo.getId());
			}
		}catch (BeansException e){
			//  开发中都需要使用这种方式进行日志输出，不能直接使用e.printStackTrace(); 会占用资源！
			log.error(e.getMessage(), e);
			//因为需要事务回滚，又因为try-catch了，为了能抛出异常回滚所以碰到异常需要抛出
			throw new GuiguException(ResultCodeEnum.SERVICE_ERROR);
			//不可达代码，抛出异常就不会执行return了
			//return false;
		}
		return true;
    }

    public void saveAlbumStat(Long albumId, String statPlay) {
        //创建对象
        AlbumStat albumStat = new AlbumStat();
        albumStat.setAlbumId(albumId);
        albumStat.setStatNum(new Random().nextInt(100000));
        albumStat.setStatType(statPlay);
        albumStatMapper.insert(albumStat);
    }


	/**
	 * 根据专辑Id查看专辑信息
	 * 在此功能中，添加了使用redission解决热点key缓存击穿的代码
	 * 核心思想是利用分布式锁保证同一时间只有一个请求能去数据库查询并更新缓存
	 * 根据key先去redis缓存中取数据，判断数据是否为空，如果数据为空说明缓存中没有数据，可能会出现缓存击穿的问题
	 * 使用redission分布式锁的思想，控制每次只有一个请求能进数据库操作即可
	 * @param albumId
	 * @return
	 */
	@TsCache(keyPrefix = "album:", keySuffix = ":info")
	@Override
	public AlbumInfo getAlbumInfo(Long albumId) {
		/*//定义String 数据类型；
		String dataKey= RedisConstant.ALBUM_INFO_PREFIX + albumId;
		try {
			//  根据缓存的key获取数据;
			AlbumInfo albumInfo = (AlbumInfo) this.redisTemplate.opsForValue().get(dataKey);
			//  判断缓存中是否存在数据;
			if(null == albumInfo) {
				System.out.println("缓存中不存在数据");
				//  不存在; 查询数据库;
				//  加锁;
				String lockKey = RedisConstant.ALBUM_LOCK_SUFFIX + albumId;
				RLock lock = redissonClient.getLock(lockKey);
				lock.lock();
				try {
					//避免当前线程释放锁之后数据已经被放到了缓存中，其他线程拿到锁继续取数据库中取数据，在判断一次缓存中有没有，有的话可以直接在缓存中取数据
					albumInfo = (AlbumInfo) this.redisTemplate.opsForValue().get(dataKey);
					if(null != albumInfo){
						//缓存中有数据直接返回
						return albumInfo;
					}
					//缓存中还是没有数据，再去数据库中取数据
					//业务逻辑;
					albumInfo = getAlbumInfoDB(albumId);
					//判断数据库是否有数据
					if (null == albumInfo) {
						//说明数据库没有数据，出现缓存穿透，给一个空对象
						this.redisTemplate.opsForValue().set(dataKey, new AlbumInfo());
						return new AlbumInfo();
					}
					//数据库中有数据,将数据放到缓存中
					this.redisTemplate.opsForValue().set(dataKey, albumInfo);
					return albumInfo;
				} catch (Exception e) {
					//异常信息
					//throw new RuntimeException(e);
					log.error(e.getMessage(), e);
				} finally {
					//解锁：
					lock.unlock();
				}
			}else {
				//  缓存有数据
				System.out.println("缓存有数据");
				return albumInfo;
			}
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		}*/
		//出现异常还有数据库兜底
		return getAlbumInfoDB(albumId);
	}

	/**
	 * 去数据库中根据专辑id查询专辑信息
	 * @param albumId
	 * @return
	 */
	private @Nullable AlbumInfo getAlbumInfoDB(Long albumId) {
		//先查询专辑信息表
		AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
		if(null !=albumInfo){
			//  根据专辑Id获取到标签数据：
			//  select * from album_attribute_value where album_id = 1630 ;
			//从数据库查询实体数据，转换为 VO 返回给前端
			LambdaQueryWrapper<AlbumAttributeValue> wrapper = new LambdaQueryWrapper<>();
			wrapper.eq(AlbumAttributeValue::getAlbumId, albumId);
			List<AlbumAttributeValue> albumAttributeValueList = albumAttributeValueMapper.selectList(wrapper);
			albumInfo.setAlbumAttributeValueVoList(albumAttributeValueList);
		}
		return albumInfo;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public void updateAlbumInfo(Long albumId, AlbumInfoVo albumInfoVo) {
		//  update album_info set title = ? where id = ?;
		AlbumInfo albumInfo = new AlbumInfo();
		//  属性拷贝：
		BeanUtils.copyProperties(albumInfoVo,albumInfo);
		albumInfo.setId(albumId);
		albumInfoMapper.updateById(albumInfo);
		//  album_attribute_value 由于修改值的时候未触发控制器，所以不能实时记录修改的信息。因此要先删除数据，再新增数据！
		albumAttributeValueMapper.delete(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId,albumId));
		List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
		//将前端传递的 VO 数据转换为数据库实体，准备执行新增 / 更新操作
		if(!CollectionUtils.isEmpty(albumAttributeValueVoList)){
			for (AlbumAttributeValueVo albumAttributeValueVo : albumAttributeValueVoList) {
				//  创建修改的实体对象,VO 对象（AlbumAttributeValueVo）用于前后端交互，实体对象（AlbumAttributeValue）用于与数据库交互，此处需要转换类型
				AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
				BeanUtils.copyProperties(albumAttributeValueVo,albumAttributeValue);
				albumAttributeValue.setAlbumId(albumId);
				albumAttributeValueMapper.insert(albumAttributeValue);
			}
		}
		//  判断当前专辑是公开; is_open=1;
		if ("1".equals(albumInfo.getIsOpen())){
			//  调用上架方法;
			rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM,MqConst.ROUTING_ALBUM_UPPER, albumInfo.getId());
			//	调用布隆过滤器
			RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
			//	将专辑id加到布隆过滤器中，为什么是专辑id而不是别的，因为用户是拿专辑id查的数据
			bloomFilter.add(albumInfo.getId());
		} else {
			//  调用下架方法;
			rabbitService.sendMessage(MqConst.EXCHANGE_ALBUM,MqConst.ROUTING_ALBUM_LOWER, albumInfo.getId());
		}
	}

	@Override
	public List<AlbumInfo> findUserAllAlbumList(Long userid) {
		LambdaQueryWrapper<AlbumInfo> wrapper = new LambdaQueryWrapper<>();
		wrapper.eq(AlbumInfo::getUserId,userid);
		wrapper.orderByDesc(AlbumInfo::getId);
		Page<AlbumInfo> albumInfoPage = new Page<>(1,100);
		Page<AlbumInfo> infoPage = albumInfoMapper.selectPage(albumInfoPage, wrapper);
		return infoPage.getRecords();
	}

	/**
	 * 通过专辑Id 获取到专辑状态信息
	 * @param albumId
	 * @return
	 */
	@TsCache(keyPrefix = "statVo:")
	@Override
	public AlbumStatVo getAlbumStatVo(Long albumId) {
		//  调用mapper层方法；
		return albumInfoMapper.selectAlbumStatVo(albumId);
	}
}
